﻿#region Copyright (c) 2013-04, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

#if TESTS

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using NUnit.Framework;


namespace Amarok.Agents
{
	[TestFixture]
	public class Test_DefaultDispatcher
	{
		private class MyMessage : Message
		{
		}

		private class MyInformation : Information
		{
		}

		private class MyOperation : Operation<String, Int32>
		{
		}

		private class UnkownMessage : Message
		{
		}

		private class TestClass
		{
			public static ConcurrentQueue<String> Calls = new ConcurrentQueue<String>();

			public static void Reset()
			{
				Calls = new ConcurrentQueue<String>();
			}

			public static Int32 CallCount(String text)
			{
				return Calls.ToArray().Count(c => c.Equals(text));
			}


			[MessageHandler, IndependentExecution, MaximumParallelismAttribute]
			private void TestHandler1(MyMessage message)
			{
				Calls.Enqueue("1");
			}

			[MessageHandler, ExclusiveExecution, NoParallelismAttribute]
			private void TestHandler2(MyMessage message)
			{
				Calls.Enqueue("2");
			}

			[MessageHandler]
			private void TestHandler3(Message message)
			{
				Calls.Enqueue("3");
			}


			[InformationHandler]
			private void TestHandler4(MyInformation message)
			{
				Calls.Enqueue("4");
			}

			[InformationHandler, ConcurrentExecution]
			private void TestHandler5(MyInformation message)
			{
				Calls.Enqueue("5");
			}

			[InformationHandler, ExclusiveExecution]
			private void TestHandler6(Information message)
			{
				Calls.Enqueue("6");
			}

			[MessageHandler]
			private void TestHandler7(MyInformation message)
			{
				Calls.Enqueue("7");
			}


			[OperationHandler, ConcurrentExecution]
			private String TestHandler8(MyOperation message)
			{
				Calls.Enqueue("8");
				return "8";
			}

			[MessageHandler, ExclusiveExecution]
			private void TestHandler9(MyOperation message)
			{
				Calls.Enqueue("9");
			}

			[MessageHandler]
			private void TestHandler10(MyOperation message)
			{
				Calls.Enqueue("10");
			}

			public void OnInitialize()
			{
				Calls.Enqueue("I");
			}
		}

		private class TestClass2
		{
			public static ConcurrentQueue<String> Calls = new ConcurrentQueue<String>();

			public static void Reset()
			{
				Calls = new ConcurrentQueue<String>();
			}

			public static Int32 CallCount(String text)
			{
				return Calls.ToArray().Count(c => c.Equals(text));
			}


			[MessageHandler, ConcurrentExecution, MaximumParallelismAttribute]
			private void TestConcurrentHandler(MyMessage message)
			{
				Calls.Enqueue("C");
			}

			public void OnInitialize()
			{
				Calls.Enqueue("I");
			}
		}

		private class TestClass3
		{
			public static ConcurrentQueue<String> Calls = new ConcurrentQueue<String>();

			public static void Reset()
			{
				Calls = new ConcurrentQueue<String>();
			}

			public static Int32 CallCount(String text)
			{
				return Calls.ToArray().Count(c => c.Equals(text));
			}


			[MessageHandler, ExclusiveExecution, OrderedProcessing]
			private void TestHandler(MyMessage message)
			{
				Calls.Enqueue("H");
				SpinWait.SpinUntil(() => false, 1000);
			}
		}


		// --- TESTS ---


		[Test]
		public void Test_Post_MyMessage()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 4);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(4));
			Assert.That(TestClass.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("1"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("2"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));
		}

		[Test]
		public void Test_Post_MyMessage_WithoutInitializeAction()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target);

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 3);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(3));
			Assert.That(TestClass.CallCount("1"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("2"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));
		}

		[Test]
		public void Test_Post_MyInformation()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			var result = dispatcher.Post(new MyInformation());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 6);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(6));
			Assert.That(TestClass.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("4"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("5"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("6"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("7"), Is.EqualTo(1));
		}

		[Test]
		public void Test_Post_MyOperation()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			var result = dispatcher.Post(new MyOperation());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 5);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(5));
			Assert.That(TestClass.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("8"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("9"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("10"), Is.EqualTo(1));
		}

		[Test]
		public void Test_Post_Null()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target);

			Assert.Throws<ArgumentNullException>(() =>
				{
					dispatcher.Post(null);
				});

			Thread.Sleep(250);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(0));
		}

		[Test]
		public void Test_Post_UnkownMessage()
		{
			TestClass2.Reset();

			var target = new TestClass2();
			var dispatcher = new DefaultDispatcher(target);

			var result = dispatcher.Post(new UnkownMessage());

			Assert.That(result, Is.False);

			Thread.Sleep(250);

			Assert.That(TestClass2.Calls.Count, Is.EqualTo(0));
		}

		[Test]
		public void Test_Complete_ProcessPendingMessages_NoMessageProcessed()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Completion.Wait();

			Assert.That(TestClass3.Calls.Count, Is.EqualTo(0));

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.False);

			Thread.Sleep(500);

			Assert.That(TestClass3.Calls.Count, Is.EqualTo(0));

			dispatcher.Complete(false);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Complete_DiscardPendingMessages_NoMessageProcessed()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Completion.Wait();

			Assert.That(TestClass3.Calls.Count, Is.EqualTo(0));

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.False);

			Thread.Sleep(500);

			Assert.That(TestClass3.Calls.Count, Is.EqualTo(0));

			dispatcher.Complete(true);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Complete_ProcessPendingMessages_HandlerRunning()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			for (Int32 i = 0; i < 5; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Thread.Sleep(250);

			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Completion.Wait();

			var calls1 = TestClass3.Calls.Count;
			Assert.That(calls1, Is.EqualTo(5));

			var result2 = dispatcher.Post(new MyMessage());

			Assert.That(result2, Is.False);

			Thread.Sleep(500);

			var call2 = TestClass3.Calls.Count;
			Assert.That(call2, Is.EqualTo(calls1));

			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Complete_DiscardPendingMessages_HandlerRunning()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			for (Int32 i = 0; i < 5; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Helper.WaitForCondition(5000, 0, () => TestClass3.Calls.Count == 1);

			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Completion.Wait();

			var calls1 = TestClass3.Calls.Count;
			Assert.That(calls1, Is.Not.EqualTo(5));
			Assert.That(calls1, Is.EqualTo(1));

			var result2 = dispatcher.Post(new MyMessage());

			Assert.That(result2, Is.False);

			Thread.Sleep(500);

			var call2 = TestClass3.Calls.Count;
			Assert.That(call2, Is.EqualTo(calls1));

			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Dispose_DiscardPendingMessages_HandlerRunning()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			for (Int32 i = 0; i < 5; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Helper.WaitForCondition(5000, 0, () => TestClass3.Calls.Count == 1);

			((IDisposable)dispatcher).Dispose();

			var calls1 = TestClass3.Calls.Count;
			Assert.That(calls1, Is.Not.EqualTo(5));
			Assert.That(calls1, Is.EqualTo(1));

			var result2 = dispatcher.Post(new MyMessage());

			Assert.That(result2, Is.False);

			Thread.Sleep(500);

			var call2 = TestClass3.Calls.Count;
			Assert.That(call2, Is.EqualTo(calls1));

			((IDisposable)dispatcher).Dispose();
		}

		[Test]
		public void Test_Complete_ProcessPendingMessages_HandlerRunning_MultipleConcurrentComplete()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			for (Int32 i = 0; i < 5; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Thread.Sleep(250);

			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Completion.Wait();

			var calls1 = TestClass3.Calls.Count;
			Assert.That(calls1, Is.EqualTo(5));

			var result2 = dispatcher.Post(new MyMessage());

			Assert.That(result2, Is.False);

			Thread.Sleep(500);

			var call2 = TestClass3.Calls.Count;
			Assert.That(call2, Is.EqualTo(calls1));

			dispatcher.Complete(discardAvailableMessages: false);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Complete_DiscardPendingMessages_HandlerRunning_MultipleConcurrentComplete()
		{
			TestClass3.Reset();

			var target = new TestClass3();
			var dispatcher = new DefaultDispatcher(target);

			for (Int32 i = 0; i < 5; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Helper.WaitForCondition(5000, 0, () => TestClass3.Calls.Count == 1);

			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Completion.Wait();

			var calls1 = TestClass3.Calls.Count;
			Assert.That(calls1, Is.Not.EqualTo(5));
			Assert.That(calls1, Is.EqualTo(1));

			var result2 = dispatcher.Post(new MyMessage());

			Assert.That(result2, Is.False);

			Thread.Sleep(500);

			var call2 = TestClass3.Calls.Count;
			Assert.That(call2, Is.EqualTo(calls1));

			dispatcher.Complete(discardAvailableMessages: true);
			dispatcher.Completion.Wait();
		}

		[Test]
		public void Test_Post_Initialize_SingleMessage()
		{
			TestClass2.Reset();

			var target = new TestClass2();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass2.Calls.Count == 2);

			Assert.That(TestClass2.Calls.Count, Is.EqualTo(2));
			Assert.That(TestClass2.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass2.CallCount("C"), Is.EqualTo(1));

			var calls = TestClass2.Calls.ToArray();
			Assert.That(calls[0], Is.EqualTo("I"));
			Assert.That(calls[1], Is.EqualTo("C"));
		}

		[Test]
		public void Test_Post_Initialize_MultipleMessages()
		{
			TestClass2.Reset();

			var target = new TestClass2();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			for (Int32 i = 0; i < 10000; i++)
			{
				var result = dispatcher.Post(new MyMessage());
				Assert.That(result, Is.True);
			}

			Helper.WaitForCondition(10000, 100, () => TestClass2.Calls.Count == 10001);

			Assert.That(TestClass2.Calls.Count, Is.EqualTo(10001));
			Assert.That(TestClass2.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass2.CallCount("C"), Is.EqualTo(10000));

			var calls = TestClass2.Calls.ToArray();
			Assert.That(calls[0], Is.EqualTo("I"));

			for (Int32 i = 0; i < 10000; i++)
				Assert.That(calls[1 + i], Is.EqualTo("C"));
		}

		[Test]
		public void Test_Post_Initialize_MultipleMessages_RepeatTestRun()
		{
			for (Int32 i = 0; i < 100; i++)
				Test_Post_Initialize_MultipleMessages();
		}

		[Test]
		public void Test_Post_Initialize_SingleMessage_NotSupportedMessage()
		{
			TestClass2.Reset();

			var target = new TestClass2();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize);

			var result = dispatcher.Post(new MyOperation());

			Assert.That(result, Is.False);

			Thread.Sleep(250);

			Assert.That(TestClass2.Calls.Count, Is.EqualTo(0));
		}

		[Test]
		public void Test_FilterCallback_Is_Null()
		{
			TestClass.Reset();

			var target = new TestClass();
			var dispatcher = new DefaultDispatcher(target, target.OnInitialize, null);

			var result = dispatcher.Post(new MyMessage());

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 4);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(4));
			Assert.That(TestClass.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("1"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("2"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));
		}

		[Test]
		public void Test_FilterCallback_Returns_True()
		{
			TestClass.Reset();

			var target = new TestClass();

			Int32 filterCalled = 0;
			Message filterMessage = null;

			Func<Message, Boolean> filter = _message =>
				{
					filterMessage = _message;
					filterCalled++;
					return true;
				};

			var dispatcher = new DefaultDispatcher(
				target,
				target.OnInitialize,
				filter);

			var message = new MyMessage();
			var result = dispatcher.Post(message);

			Assert.That(result, Is.True);

			Helper.WaitForCondition(10000, 100, () => TestClass.Calls.Count == 4);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(4));
			Assert.That(TestClass.CallCount("I"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("1"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("2"), Is.EqualTo(1));
			Assert.That(TestClass.CallCount("3"), Is.EqualTo(1));

			Assert.That(filterCalled, Is.EqualTo(1));
			Assert.That(filterMessage, Is.SameAs(message));
		}

		[Test]
		public void Test_FilterCallback_Returns_False()
		{
			TestClass.Reset();

			var target = new TestClass();

			Int32 filterCalled = 0;
			Message filterMessage = null;

			Func<Message, Boolean> filter = _message =>
			{
				filterMessage = _message;
				filterCalled++;
				return false;
			};

			var dispatcher = new DefaultDispatcher(
				target,
				target.OnInitialize,
				filter);

			var message = new MyMessage();
			var result = dispatcher.Post(message);

			Assert.That(result, Is.False);

			Helper.WaitForCondition(500, 100, () => TestClass.Calls.Count == 4);

			Assert.That(TestClass.Calls.Count, Is.EqualTo(0));
			Assert.That(filterCalled, Is.EqualTo(1));
			Assert.That(filterMessage, Is.SameAs(message));
		}

	}
}

#endif
